// --------------------------------------------------------------------------------------------------------------------- 
// <copyright file="SyncAllProcessor.cs" company="Scrum for Team System">
//   None
// </copyright>
// <summary>
//   Defines the SyncAllProcessor type.
// </summary>
// ---------------------------------------------------------------------------------------------------------------------

namespace ScrumForTeamSystem.EventService.TransitionService
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.ComponentModel.Composition;
    using System.Globalization;
    using System.Linq;

    using DataStructures;

    using Infrastructure;
    using Infrastructure.Interfaces;

    using Microsoft.TeamFoundation.WorkItemTracking.Client;

    using Properties;

    using ScrumForTeamSystem.EventService.Services;

    /// <summary>
    /// Defines the SyncAllProcessor type.
    /// </summary>
    [Export(typeof(ITfsEventProcessor))]
    public class SyncAllProcessor : ITfsEventProcessor, IMessageProducer, IDisposable
    {
        /// <summary>
        /// The work item store repository.
        /// </summary>
        private readonly IWorkItemStoreRepository workItemStoreRepository;

        /// <summary>
        /// The background worker object.
        /// </summary>
        private BackgroundWorker worker;

        /// <summary>
        /// The base work item query.
        /// </summary>
        private string baseQuery;

        /// <summary>
        /// The [is first run] flag.
        /// </summary>
        private bool isFirstRun;

        /// <summary>
        /// the [is running] flag.
        /// </summary>
        private bool isRunning;

        /// <summary>
        /// The calculatrion rules collection.
        /// </summary>
        private TransitionRulesCollection rulesCollection;

        /// <summary>
        /// Initializes a new instance of the <see cref="SyncAllProcessor"/> class.
        /// </summary>
        public SyncAllProcessor() : this(DependencyResolver.GetImplementation<IWorkItemStoreRepository>())
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="SyncAllProcessor"/> class.
        /// </summary>
        /// <param name="workItemStoreRepository">
        /// The work item store repository.
        /// </param>
        public SyncAllProcessor(IWorkItemStoreRepository workItemStoreRepository)
        {
            this.workItemStoreRepository = workItemStoreRepository;
            this.isFirstRun = true;
        }

        /// <summary>
        /// Finalizes an instance of the <see cref="SyncAllProcessor"/> class. 
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="SyncAllProcessor"/> is reclaimed by garbage collection.
        /// </summary>
        ~SyncAllProcessor()
        {
            this.Dispose(true);
        }

        /// <summary>
        /// Occurs when [ProcessMessage] event raised.
        /// </summary>
        public event EventHandler<ActionEventArgs> Message;

        /// <summary>
        /// Occurs when [exception ProcessMessage] event raised.
        /// </summary>
        public event EventHandler<ExceptionActionEventArgs> ExceptionMessage;

        /// <summary>
        /// Processes the specified event instance.
        /// </summary>
        /// <param name="eventInstance">The event instance.</param>
        public void Process(ITfsEvent eventInstance)
        {
            if (eventInstance == null)
            {
                throw new ArgumentNullException("eventInstance");
            }

            SyncAllEvent syncAllEvent;

            if (!GlobalHelpers.TryCastEvent(eventInstance, out syncAllEvent))
            {
                return;
            }

            this.StartProcess(new KeyValuePair<string, SyncAllEvent>(eventInstance.ServerIdentity.Url, syncAllEvent));
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.worker != null)
                {
                    this.worker.Dispose();
                }
            }
        }

        /// <summary>
        /// Loads the rules.
        /// </summary>
        private void LoadRulesAndBuildBaseQuery()
        {
            this.rulesCollection = ResourceHelper.LoadRules();

            var typeNames = string.Empty;

            foreach (var typeName in this.rulesCollection.Select(r => r.WorkItemTypeName.Target).Distinct())
            {
                typeNames = string.Concat(
                        typeNames,
                        string.IsNullOrEmpty(typeNames) ? string.Empty : ",",
                        "'",
                        typeName,
                        "'");
            }

            this.baseQuery = string.Format(CultureInfo.InvariantCulture, Settings.Default.SyncQuery, "{0}", typeNames);

            this.RaiseMessage(Resources.String001);
        }

        /// <summary>
        /// Starts the process.
        /// </summary>
        /// <param name="argsPair">The args pair.</param>
        private void StartProcess(KeyValuePair<string, SyncAllEvent> argsPair)
        {
            if (this.isRunning)
            {
                this.RaiseMessage(Resources.String002);
                return;
            }

            this.isRunning = true;

            this.worker = new BackgroundWorker();
            this.worker.DoWork += this.OnDoWork;
            this.worker.RunWorkerCompleted += this.OnRunWorkerCompleted;
            this.worker.RunWorkerAsync(argsPair);

            this.RaiseMessage(Resources.String003);
        }

        /// <summary>
        /// Raises the message.
        /// </summary>
        /// <param name="message">The message.</param>
        private void RaiseMessage(string message)
        {
            if (this.Message == null)
            {
                return;
            }

            this.Message(this, new ActionEventArgs(message));
        }

        /// <summary>
        /// Called when background worker begins async process.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The evetn arguments.</param>
        private void OnDoWork(object sender, DoWorkEventArgs e)
        {
            if (!(e.Argument is KeyValuePair<string, SyncAllEvent>))
            {
                return;
            }

            // Sleep the thread to reduce chance of work item save conflicts.
            this.RaiseMessage(Resources.String004);
            System.Threading.Thread.Sleep(6000);

            if (this.isFirstRun)
            {
                this.LoadRulesAndBuildBaseQuery();
                this.isFirstRun = false;
            }

            var valuePair = (KeyValuePair<string, SyncAllEvent>)e.Argument;

            var workItemStore = this.workItemStoreRepository.GetWorkItemStore(valuePair.Key, Guid.NewGuid().ToString());

            if (workItemStore == null)
            {
                this.RaiseMessage(Resources.String005);
                return;
            }

            var updateWorkItem = new Updater();

            updateWorkItem.ProcessMessage += this.RaiseMessage;

            var query = string.Format(
                    CultureInfo.InvariantCulture,
                    this.baseQuery,
                    valuePair.Value.ProjectName);

            var workItemIds = workItemStore.Query(query).OfType<WorkItem>().Select(w => w.Id).ToArray();

            this.RaiseMessage(
                    string.Format(
                            CultureInfo.InvariantCulture,
                            Resources.String006,
                            query,
                            workItemIds.Count()));

            foreach (var workItemId in workItemIds)
            {
                workItemStore.RefreshCache();

                var workItem = workItemStore.GetWorkItem(workItemId);

                var rules = this.rulesCollection.GetAllSyncRules(workItem.Type.Name)
                    .ToArray();

                try
                {
                    updateWorkItem.Synchronise(workItem, rules);
                }
                catch (Exception ex)
                {
                    var message = string.Format(
                                    CultureInfo.InvariantCulture,
                                    Resources.String007,
                                    workItemId,
                                    ex.Message);

                    var serviceException = new ServiceException(Settings.Default.ServiceName, message, ex);

                    if (this.ExceptionMessage != null)
                    {
                        this.ExceptionMessage(this, new ExceptionActionEventArgs(serviceException));
                    }
                    else
                    {
                        throw serviceException;
                    }
                }
            }
        }

        /// <summary>
        /// Called when [worker completed].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The event args.</param>
        private void OnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            this.isRunning = false;

            if (e.Error != null)
            {
                var serviceException = new ServiceException(
                        Settings.Default.ServiceName, Resources.String008, e.Error);

                if (this.ExceptionMessage != null)
                {
                    this.ExceptionMessage(this, new ExceptionActionEventArgs(e.Error));
                }
                else
                {
                    throw serviceException;
                }
            }

            this.RaiseMessage(Resources.String009);
        }
    }
}